<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<bindings id="browserBindings"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

  <binding id="browser" extends="xul:browser" role="outerdoc">
    <content clickthrough="never">
      <children/>
    </content>
    <implementation type="application/javascript" implements="nsIObserver, nsIDOMEventListener, nsIMessageListener, nsIBrowser">
      <property name="autoscrollEnabled">
        <getter>
          <![CDATA[
            if (this.getAttribute("autoscroll") == "false")
              return false;

            return this.mPrefs.getBoolPref("general.autoScroll", true);
          ]]>
        </getter>
      </property>

      <property name="canGoBack"
                onget="return this.webNavigation.canGoBack;"
                readonly="true"/>

      <property name="canGoForward"
                onget="return this.webNavigation.canGoForward;"
                readonly="true"/>

      <method name="_wrapURIChangeCall">
        <parameter name="fn"/>
        <body>
          <![CDATA[
            this.inLoadURI = true;
            try {
              fn();
            } finally {
              this.inLoadURI = false;
            }
          ]]>
        </body>
      </method>


      <method name="goBack">
        <body>
          <![CDATA[
            var webNavigation = this.webNavigation;
            if (webNavigation.canGoBack)
              this._wrapURIChangeCall(() => webNavigation.goBack());
          ]]>
        </body>
      </method>

      <method name="goForward">
        <body>
          <![CDATA[
            var webNavigation = this.webNavigation;
            if (webNavigation.canGoForward)
              this._wrapURIChangeCall(() => webNavigation.goForward());
          ]]>
        </body>
      </method>

      <method name="reload">
        <body>
          <![CDATA[
            const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
            const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
            this.reloadWithFlags(flags);
          ]]>
        </body>
      </method>

      <method name="reloadWithFlags">
        <parameter name="aFlags"/>
        <body>
          <![CDATA[
            this.webNavigation.reload(aFlags);
          ]]>
        </body>
      </method>

      <method name="stop">
        <body>
          <![CDATA[
            const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
            const flags = nsIWebNavigation.STOP_ALL;
            this.webNavigation.stop(flags);
          ]]>
        </body>
      </method>

      <!-- throws exception for unknown schemes -->
      <method name="loadURI">
        <parameter name="aURI"/>
        <parameter name="aReferrerURI"/>
        <parameter name="aCharset"/>
        <body>
          <![CDATA[
            const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
            const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
            this._wrapURIChangeCall(() =>
              this.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset));
          ]]>
        </body>
      </method>

      <!-- throws exception for unknown schemes -->
      <method name="loadURIWithFlags">
        <parameter name="aURI"/>
        <parameter name="aFlags"/>
        <parameter name="aReferrerURI"/>
        <parameter name="aCharset"/>
        <parameter name="aPostData"/>
        <body>
          <![CDATA[
            if (!aURI)
              aURI = "about:blank";

            var aReferrerPolicy = Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT;
            var aTriggeringPrincipal;

            // Check for loadURIWithFlags(uri, { ... });
            var params = arguments[1];
            if (params && typeof(params) == "object") {
              aFlags = params.flags;
              aReferrerURI = params.referrerURI;
              if ('referrerPolicy' in params) {
                aReferrerPolicy = params.referrerPolicy;
              }
              if ("triggeringPrincipal" in params) {
                aTriggeringPrincipal = params.triggeringPrincipal;
              }
              aCharset = params.charset;
              aPostData = params.postData;
            }

            this._wrapURIChangeCall(() =>
              this.webNavigation.loadURIWithOptions(
                  aURI, aFlags, aReferrerURI, aReferrerPolicy,
                  aPostData, null, null, aTriggeringPrincipal));
          ]]>
        </body>
      </method>

      <method name="goHome">
        <body>
          <![CDATA[
            try {
              this.loadURI(this.homePage);
            }
            catch (e) {
            }
          ]]>
        </body>
      </method>

      <property name="homePage">
        <getter>
          <![CDATA[
            var uri;

            if (this.hasAttribute("homepage"))
              uri = this.getAttribute("homepage");
            else
              uri = "http://www.mozilla.org/"; // widget pride

            return uri;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            this.setAttribute("homepage", val);
            return val;
          ]]>
        </setter>
      </property>

      <method name="gotoIndex">
        <parameter name="aIndex"/>
        <body>
          <![CDATA[
            this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex));
          ]]>
        </body>
      </method>

      <property name="currentURI" readonly="true">
       <getter><![CDATA[
          if (this.webNavigation) {
            return this.webNavigation.currentURI;
          }
          return null;
       ]]>
       </getter>
      </property>

      <!--
        Used by session restore to ensure that currentURI is set so
        that switch-to-tab works before the tab is fully
        restored. This function also invokes onLocationChanged
        listeners in tabbrowser.xml.
      -->
      <method name="_setCurrentURI">
        <parameter name="aURI"/>
        <body><![CDATA[
          this.docShell.setCurrentURI(aURI);
        ]]></body>
      </method>

      <property name="documentURI"
                onget="return this.contentDocument.documentURIObject;"
                readonly="true"/>

      <property name="documentContentType"
                onget="return this.contentDocument ? this.contentDocument.contentType : null;"
                readonly="true"/>

      <property name="preferences"
                onget="return this.mPrefs.QueryInterface(Components.interfaces.nsIPrefService);"
                readonly="true"/>

      <!--
        Weak reference to the related browser (see
        nsIBrowser.getRelatedBrowser).
      -->
      <field name="_relatedBrowser">null</field>
      <property name="relatedBrowser">
        <getter><![CDATA[
          return this._relatedBrowser && this._relatedBrowser.get();
        ]]></getter>
        <setter><![CDATA[
          this._relatedBrowser = Cu.getWeakReference(val);
        ]]></setter>
      </property>

      <field name="_docShell">null</field>

      <property name="docShell" readonly="true">
        <getter><![CDATA[
          if (this._docShell)
            return this._docShell;

          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
          if (!frameLoader)
            return null;
          this._docShell = frameLoader.docShell;
          return this._docShell;
        ]]></getter>
      </property>

      <field name="_loadContext">null</field>

      <property name="loadContext" readonly="true">
        <getter><![CDATA[
          if (this._loadContext)
            return this._loadContext;

          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
          if (!frameLoader)
            return null;
          this._loadContext = frameLoader.loadContext;
          return this._loadContext;
        ]]></getter>
      </property>

      <property name="autoCompletePopup"
                onget="return document.getElementById(this.getAttribute('autocompletepopup'))"
                readonly="true"/>

      <property name="dateTimePicker"
                onget="return document.getElementById(this.getAttribute('datetimepicker'))"
                readonly="true"/>

      <property name="docShellIsActive">
        <getter>
          <![CDATA[
            return this.docShell && this.docShell.isActive;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            if (this.docShell)
              return this.docShell.isActive = val;
            return false;
          ]]>
        </setter>
      </property>

      <method name="preserveLayers">
        <parameter name="preserve"/>
        <body>
          <!-- Only useful for remote browsers. -->
        </body>
      </method>

      <method name="makePrerenderedBrowserActive">
        <body>
        <![CDATA[
          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
          if (frameLoader) {
            frameLoader.makePrerenderedLoaderActive();
          }
        ]]>
        </body>
      </method>

      <property name="imageDocument"
                readonly="true">
        <getter>
          <![CDATA[
            var document = this.contentDocument;
            if (!document || !(document instanceof Components.interfaces.nsIImageDocument))
              return null;

            try {
                return {width: document.imageRequest.image.width, height: document.imageRequest.image.height };
            } catch (e) {}
            return null;
          ]]>
        </getter>
      </property>

      <!-- stubbed to false until all callers are removed -->
      <property name="isRemoteBrowser"
                onget="return false;"
                readonly="true"/>

      <property name="messageManager"
                readonly="true">
        <getter>
          <![CDATA[
            var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            if (!owner.frameLoader) {
              return null;
            }
            return owner.frameLoader.messageManager;
          ]]>
        </getter>

      </property>

      <field name="_webNavigation">null</field>

      <property name="webNavigation"
                readonly="true">
        <getter>
        <![CDATA[
          if (!this._webNavigation) {
            if (!this.docShell) {
              return null;
            }
            this._webNavigation = this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);
          }
          return this._webNavigation;
        ]]>
        </getter>
      </property>

      <field name="_webBrowserFind">null</field>

      <property name="webBrowserFind"
                readonly="true">
        <getter>
        <![CDATA[
          if (!this._webBrowserFind)
            this._webBrowserFind = this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebBrowserFind);
          return this._webBrowserFind;
        ]]>
        </getter>
      </property>

      <method name="getTabBrowser">
        <body>
          <![CDATA[
            for (let node = this.parentNode; node instanceof Element; node = node.parentNode) {
              if (node.localName == "tabbrowser")
                return node;
            }
            return null;
          ]]>
        </body>
      </method>

      <field name="_finder">null</field>

      <property name="finder" readonly="true">
        <getter><![CDATA[
          if (!this._finder) {
            if (!this.docShell)
              return null;

            let Finder = Components.utils.import("resource://gre/modules/Finder.jsm", {}).Finder;
            this._finder = new Finder(this.docShell);
          }
          return this._finder;
        ]]></getter>
      </property>

      <field name="_fastFind">null</field>
      <property name="fastFind" readonly="true">
        <getter><![CDATA[
          if (!this._fastFind) {
            if (!("@mozilla.org/typeaheadfind;1" in Components.classes))
              return null;

            var tabBrowser = this.getTabBrowser();
            if (tabBrowser && "fastFind" in tabBrowser)
              return this._fastFind = tabBrowser.fastFind;

            if (!this.docShell)
              return null;

            this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
                                       .createInstance(Components.interfaces.nsITypeAheadFind);
            this._fastFind.init(this.docShell);
          }
          return this._fastFind;
        ]]></getter>
      </property>

      <property name="outerWindowID" readonly="true">
        <getter><![CDATA[
          return this.contentWindow
                     .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                     .getInterface(Components.interfaces.nsIDOMWindowUtils)
                     .outerWindowID;
        ]]></getter>
      </property>

      <property name="innerWindowID" readonly="true">
        <getter><![CDATA[
          try {
            return this.contentWindow
                       .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                       .getInterface(Components.interfaces.nsIDOMWindowUtils)
                       .currentInnerWindowID;
          } catch (e) {
            if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
              throw e;
            }
            return null;
          }
        ]]></getter>
      </property>

      <field name="_lastSearchString">null</field>

      <property name="webProgress"
                readonly="true"
                onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);"/>

      <field name="_contentWindow">null</field>

      <property name="contentWindow"
                readonly="true"
                onget="return this._contentWindow || (this._contentWindow = this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow));"/>

      <property name="contentWindowAsCPOW"
                readonly="true"
                onget="return this.contentWindow;"/>

      <property name="sessionHistory"
                onget="return this.webNavigation.sessionHistory;"
                readonly="true"/>

      <property name="markupDocumentViewer"
                onget="return this.docShell.contentViewer;"
                readonly="true"/>

      <property name="contentViewerEdit"
                onget="return this.docShell.contentViewer.QueryInterface(Components.interfaces.nsIContentViewerEdit);"
                readonly="true"/>

      <property name="contentViewerFile"
                onget="return this.docShell.contentViewer.QueryInterface(Components.interfaces.nsIContentViewerFile);"
                readonly="true"/>

      <property name="contentDocument"
                onget="return this.webNavigation.document;"
                readonly="true"/>

      <property name="contentDocumentAsCPOW"
                onget="return this.contentDocument;"
                readonly="true"/>

      <property name="contentTitle"
                onget="return this.contentDocument.title;"
                readonly="true"/>

      <property name="characterSet"
                onget="return this.docShell.charset;">
        <setter><![CDATA[
          this.docShell.charset = val;
          this.docShell.gatherCharsetMenuTelemetry();
        ]]></setter>
      </property>

      <property name="mayEnableCharacterEncodingMenu"
                onget="return this.docShell.mayEnableCharacterEncodingMenu;"
                readonly="true"/>

      <property name="contentPrincipal"
                onget="return this.contentDocument.nodePrincipal;"
                readonly="true"/>

      <property name="showWindowResizer"
                onset="if (val) this.setAttribute('showresizer', 'true');
                       else this.removeAttribute('showresizer');
                       return val;"
                onget="return this.getAttribute('showresizer') == 'true';"/>

      <property name="manifestURI"
                readonly="true">
        <getter><![CDATA[
          return this.contentDocument.documentElement &&
                 this.contentDocument.documentElement.getAttribute("manifest");
        ]]></getter>
      </property>

      <property name="fullZoom">
        <getter><![CDATA[
          return this.markupDocumentViewer.fullZoom;
        ]]></getter>
        <setter><![CDATA[
          this.markupDocumentViewer.fullZoom = val;
        ]]></setter>
      </property>

      <property name="textZoom">
        <getter><![CDATA[
          return this.markupDocumentViewer.textZoom;
        ]]></getter>
        <setter><![CDATA[
          this.markupDocumentViewer.textZoom = val;
        ]]></setter>
      </property>

      <property name="isSyntheticDocument">
        <getter><![CDATA[
          return this.contentDocument.mozSyntheticDocument;
        ]]></getter>
      </property>

      <property name="hasContentOpener">
        <getter><![CDATA[
          return !!this.contentWindow.opener;
        ]]></getter>
      </property>

      <field name="mPrefs" readonly="true">
        Components.classes['@mozilla.org/preferences-service;1']
                  .getService(Components.interfaces.nsIPrefBranch);
      </field>

      <field name="_mStrBundle">null</field>

      <property name="mStrBundle">
        <getter>
        <![CDATA[
          if (!this._mStrBundle) {
            // need to create string bundle manually instead of using <xul:stringbundle/>
            // see bug 63370 for details
            this._mStrBundle = Components.classes["@mozilla.org/intl/stringbundle;1"]
                                         .getService(Components.interfaces.nsIStringBundleService)
                                         .createBundle("chrome://global/locale/browser.properties");
          }
          return this._mStrBundle;
        ]]></getter>
      </property>

      <method name="addProgressListener">
        <parameter name="aListener"/>
        <parameter name="aNotifyMask"/>
        <body>
          <![CDATA[
            if (!aNotifyMask) {
              aNotifyMask = Components.interfaces.nsIWebProgress.NOTIFY_ALL;
            }
            this.webProgress.addProgressListener(aListener, aNotifyMask);
          ]]>
        </body>
      </method>

      <method name="removeProgressListener">
        <parameter name="aListener"/>
        <body>
          <![CDATA[
            this.webProgress.removeProgressListener(aListener);
         ]]>
        </body>
      </method>

      <method name="findChildShell">
        <parameter name="aDocShell"/>
        <parameter name="aSoughtURI"/>
        <body>
          <![CDATA[
            if (aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation)
                         .currentURI.spec == aSoughtURI.spec)
              return aDocShell;
            var node = aDocShell.QueryInterface(
                                   Components.interfaces.nsIDocShellTreeItem);
            for (var i = 0; i < node.childCount; ++i) {
              var docShell = node.getChildAt(i);
              docShell = this.findChildShell(docShell, aSoughtURI);
              if (docShell)
                return docShell;
            }
            return null;
          ]]>
        </body>
      </method>

      <method name="onPageHide">
        <parameter name="aEvent"/>
        <body>
          <![CDATA[
            // Delete the feeds cache if we're hiding the topmost page
            // (as opposed to one of its iframes).
            if (this.feeds && aEvent.target == this.contentDocument)
              this.feeds = null;
            if (!this.docShell || !this.fastFind)
              return;
            var tabBrowser = this.getTabBrowser();
            if (!tabBrowser || !("fastFind" in tabBrowser) ||
                tabBrowser.selectedBrowser == this)
              this.fastFind.setDocShell(this.docShell);
         ]]>
        </body>
      </method>

      <method name="updateBlockedPopups">
        <body>
          <![CDATA[
            let event = document.createEvent("Events");
            event.initEvent("DOMUpdatePageReport", true, true);
            this.dispatchEvent(event);
          ]]>
        </body>
      </method>

      <method name="retrieveListOfBlockedPopups">
        <body>
          <![CDATA[
          this.messageManager.sendAsyncMessage("PopupBlocking:GetBlockedPopupList", null);
          return new Promise(resolve => {
            let self = this;
            this.messageManager.addMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
              function replyReceived(msg) {
                self.messageManager.removeMessageListener("PopupBlocking:ReplyGetBlockedPopupList",
                                                          replyReceived);
                resolve(msg.data.popupData);
              }
            );
          });
          ]]>
        </body>
      </method>

      <method name="unblockPopup">
        <parameter name="aPopupIndex"/>
        <body><![CDATA[
          this.messageManager.sendAsyncMessage("PopupBlocking:UnblockPopup",
                                               {index: aPopupIndex});
        ]]></body>
      </method>

      <field name="blockedPopups">null</field>

      <!-- Obsolete name for blockedPopups. Used by android. -->
      <property name="pageReport"
         onget="return this.blockedPopups;"
         readonly="true"/>

      <method name="audioPlaybackStarted">
        <body>
          <![CDATA[
            let event = document.createEvent("Events");
            event.initEvent("DOMAudioPlaybackStarted", true, false);
            this.dispatchEvent(event);
            if (this._audioBlocked) {
              this._audioBlocked = false;
              event = document.createEvent("Events");
              event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
              this.dispatchEvent(event);
            }
          ]]>
        </body>
      </method>

      <method name="audioPlaybackStopped">
        <body>
          <![CDATA[
            let event = document.createEvent("Events");
            event.initEvent("DOMAudioPlaybackStopped", true, false);
            this.dispatchEvent(event);
          ]]>
        </body>
      </method>

      <method name="audioPlaybackBlocked">
        <body>
          <![CDATA[
            this._audioBlocked = true;
            let event = document.createEvent("Events");
            event.initEvent("DOMAudioPlaybackBlockStarted", true, false);
            this.dispatchEvent(event);
          ]]>
        </body>
      </method>

      <field name="_audioMuted">false</field>
      <property name="audioMuted"
                onget="return this._audioMuted;"
                readonly="true"/>


      <field name="_audioBlocked">false</field>
      <property name="audioBlocked"
                onget="return this._audioBlocked;"
                readonly="true"/>

      <method name="mute">
        <body>
          <![CDATA[
            this._audioMuted = true;
            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: "mute"});
          ]]>
        </body>
      </method>

      <method name="unmute">
        <body>
          <![CDATA[
            this._audioMuted = false;
            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: "unmute"});
          ]]>
        </body>
      </method>

      <method name="pauseMedia">
        <parameter name="disposable"/>
        <body>
          <![CDATA[
            let suspendedReason;
            if (disposable) {
              suspendedReason = "mediaControlPaused";
            } else {
              suspendedReason = "lostAudioFocusTransiently";
            }

            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: suspendedReason});
          ]]>
        </body>
      </method>

      <method name="stopMedia">
        <body>
          <![CDATA[
            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: "mediaControlStopped"});
          ]]>
        </body>
      </method>

      <method name="blockMedia">
        <body>
          <![CDATA[
            this._audioBlocked = true;
            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: "blockInactivePageMedia"});
          ]]>
        </body>
      </method>

      <method name="resumeMedia">
        <body>
          <![CDATA[
            this._audioBlocked = false;
            this.messageManager.sendAsyncMessage("AudioPlayback",
                                                 {type: "resumeMedia"});
            let  event = document.createEvent("Events");
            event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
            this.dispatchEvent(event);
          ]]>
        </body>
      </method>

      <property name="securityUI">
        <getter>
          <![CDATA[
            if (!this.docShell.securityUI) {
              const SECUREBROWSERUI_CONTRACTID = "@mozilla.org/secure_browser_ui;1";
              if (!this.hasAttribute("disablesecurity") &&
                  SECUREBROWSERUI_CONTRACTID in Components.classes) {
                var securityUI = Components.classes[SECUREBROWSERUI_CONTRACTID]
                                           .createInstance(Components.interfaces.nsISecureBrowserUI);
                securityUI.init(this.contentWindow);
              }
            }

            return this.docShell.securityUI;
          ]]>
        </getter>
        <setter>
          <![CDATA[
            this.docShell.securityUI = val;
          ]]>
        </setter>
      </property>

      <!-- increases or decreases the browser's network priority -->
      <method name="adjustPriority">
        <parameter name="adjustment"/>
        <body><![CDATA[
          let loadGroup = this.webNavigation.QueryInterface(Components.interfaces.nsIDocumentLoader)
                              .loadGroup.QueryInterface(Components.interfaces.nsISupportsPriority);
          loadGroup.adjustPriority(adjustment);
        ]]></body>
      </method>

      <!-- sets the browser's network priority to a discrete value -->
      <method name="setPriority">
        <parameter name="priority"/>
        <body><![CDATA[
          let loadGroup = this.webNavigation.QueryInterface(Components.interfaces.nsIDocumentLoader)
                              .loadGroup.QueryInterface(Components.interfaces.nsISupportsPriority);
          loadGroup.priority = priority;
        ]]></body>
      </method>

      <field name="urlbarChangeTracker">
        ({
          _startedLoadSinceLastUserTyping: false,

          startedLoad() {
            this._startedLoadSinceLastUserTyping = true;
          },
          finishedLoad() {
            this._startedLoadSinceLastUserTyping = false;
          },
          userTyped() {
            this._startedLoadSinceLastUserTyping = false;
          },
        })
      </field>

      <method name="didStartLoadSinceLastUserTyping">
        <body><![CDATA[
          return !this.inLoadURI &&
                 this.urlbarChangeTracker._startedLoadSinceLastUserTyping;
        ]]></body>
      </method>

      <field name="_userTypedValue">
        null
      </field>

      <property name="userTypedValue"
                onget="return this._userTypedValue;">
        <setter><![CDATA[
          this.urlbarChangeTracker.userTyped();
          this._userTypedValue = val;
          return val;
        ]]></setter>
      </property>

      <field name="mFormFillAttached">
        false
      </field>

      <field name="isShowingMessage">
        false
      </field>

      <field name="droppedLinkHandler">
        null
      </field>

      <field name="mIconURL">null</field>

      <property name="isAuthDOSProtected"
                onget="return (this.getAttribute('authdosprotected') == 'true');"
                readonly="true"/>

      <!-- This is managed by the tabbrowser -->
      <field name="lastURI">null</field>

      <field name="mDestroyed">false</field>

      <constructor>
        <![CDATA[
          try {
            // |webNavigation.sessionHistory| will have been set by the frame
            // loader when creating the docShell as long as this xul:browser
            // doesn't have the 'disablehistory' attribute set.
            if (this.docShell && this.webNavigation.sessionHistory) {
              var os = Components.classes["@mozilla.org/observer-service;1"]
                                 .getService(Components.interfaces.nsIObserverService);
              os.addObserver(this, "browser:purge-session-history", true);

              // enable global history if we weren't told otherwise
              if (!this.hasAttribute("disableglobalhistory")) {
                try {
                  this.docShell.useGlobalHistory = true;
                } catch (ex) {
                  // This can occur if the Places database is locked
                  Components.utils.reportError("Error enabling browser global history: " + ex);
                }
              }
            }
          }
          catch (e) {
            Components.utils.reportError(e);
          }
          try {
            var securityUI = this.securityUI;
          }
          catch (e) {
          }

          // XXX tabbrowser.xml sets "relatedBrowser" as a direct property on
          // some browsers before they are put into a DOM (and get a binding).
          // This hack makes sure that we hold a weak reference to the other
          // browser (and go through the proper getter and setter).
          if (this.hasOwnProperty("relatedBrowser")) {
            var relatedBrowser = this.relatedBrowser;
            delete this.relatedBrowser;
            this.relatedBrowser = relatedBrowser;
          }

          this.addEventListener("pagehide", this.onPageHide, true);

          if (this.messageManager) {
            this.messageManager.addMessageListener("PopupBlocking:UpdateBlockedPopups", this);
            this.messageManager.addMessageListener("Autoscroll:Start", this);
            this.messageManager.addMessageListener("Autoscroll:Cancel", this);
            this.messageManager.addMessageListener("AudioPlayback:Start", this);
            this.messageManager.addMessageListener("AudioPlayback:Stop", this);
            this.messageManager.addMessageListener("AudioPlayback:Block", this);
          }
        ]]>
      </constructor>

      <destructor>
        <![CDATA[
          this.destroy();
        ]]>
      </destructor>

      <!-- This is necessary because the destructor doesn't always get called when
           we are removed from a tabbrowser. This will be explicitly called by tabbrowser.

           Note: this function is overriden in remote-browser.xml, so any clean-up that
           also applies to browser.isRemoteBrowser = true must be duplicated there. -->
      <method name="destroy">
        <body>
          <![CDATA[
          if (this.mDestroyed)
            return;
          this.mDestroyed = true;

          if (this.docShell && this.webNavigation.sessionHistory) {
            var os = Components.classes["@mozilla.org/observer-service;1"]
                               .getService(Components.interfaces.nsIObserverService);
            try {
              os.removeObserver(this, "browser:purge-session-history");
            } catch (ex) {
              // It's not clear why this sometimes throws an exception.
            }
          }

          this._fastFind = null;
          this._webBrowserFind = null;

          // The feeds cache can keep the document inside this browser alive.
          this.feeds = null;

          this.lastURI = null;

          this.removeEventListener("pagehide", this.onPageHide, true);

          if (this._autoScrollNeedsCleanup) {
            // we polluted the global scope, so clean it up
            this._autoScrollPopup.parentNode.removeChild(this._autoScrollPopup);
          }
          ]]>
        </body>
      </method>

      <!--
        We call this _receiveMessage (and alias receiveMessage to it) so that
        bindings that inherit from this one can delegate to it.
      -->
      <method name="_receiveMessage">
        <parameter name="aMessage"/>
        <body><![CDATA[
          let data = aMessage.data;
          switch (aMessage.name) {
            case "PopupBlocking:UpdateBlockedPopups": {
              this.blockedPopups = {
                length: data.count,
                reported: !data.freshPopup,
              };

              this.updateBlockedPopups();
              break;
            }
            case "Autoscroll:Start": {
              if (!this.autoscrollEnabled) {
                return false;
              }
              this.startScroll(data.scrolldir, data.screenX, data.screenY);
              return true;
            }
            case "Autoscroll:Cancel":
              this._autoScrollPopup.hidePopup();
              break;
            case "AudioPlayback:Start":
              this.audioPlaybackStarted();
              break;
            case "AudioPlayback:Stop":
              this.audioPlaybackStopped();
              break;
            case "AudioPlayback:Block":
              this.audioPlaybackBlocked();
              break;
          }
          return undefined;
        ]]></body>
      </method>

      <method name="receiveMessage">
        <parameter name="aMessage"/>
        <body><![CDATA[
          return this._receiveMessage(aMessage);
        ]]></body>
      </method>

      <method name="observe">
        <parameter name="aSubject"/>
        <parameter name="aTopic"/>
        <parameter name="aState"/>
        <body>
          <![CDATA[
            if (aTopic != "browser:purge-session-history")
              return;

            this.purgeSessionHistory();
          ]]>
        </body>
      </method>

      <method name="purgeSessionHistory">
        <body>
          <![CDATA[
            this.messageManager.sendAsyncMessage("Browser:PurgeSessionHistory");
          ]]>
        </body>
      </method>

      <method name="createAboutBlankContentViewer">
        <parameter name="aPrincipal"/>
        <body>
          <![CDATA[
            let principal = BrowserUtils.principalWithMatchingOA(aPrincipal, this.contentPrincipal);
            this.docShell.createAboutBlankContentViewer(principal);
          ]]>
        </body>
      </method>

      <field name="_AUTOSCROLL_SNAP">10</field>
      <field name="_scrolling">false</field>
      <field name="_startX">null</field>
      <field name="_startY">null</field>
      <field name="_autoScrollPopup">null</field>
      <field name="_autoScrollNeedsCleanup">false</field>

      <method name="stopScroll">
        <body>
          <![CDATA[
            if (this._scrolling) {
              this._scrolling = false;
              window.removeEventListener("mousemove", this, true);
              window.removeEventListener("mousedown", this, true);
              window.removeEventListener("mouseup", this, true);
              window.removeEventListener("DOMMouseScroll", this, true);
              window.removeEventListener("contextmenu", this, true);
              window.removeEventListener("keydown", this, true);
              window.removeEventListener("keypress", this, true);
              window.removeEventListener("keyup", this, true);
              this.messageManager.sendAsyncMessage("Autoscroll:Stop");
            }
         ]]>
       </body>
     </method>

      <method name="_createAutoScrollPopup">
        <body>
          <![CDATA[
            const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
            var popup = document.createElementNS(XUL_NS, "panel");
            popup.className = "autoscroller";
            // We set this attribute on the element so that mousemove
            // events can be handled by browser-content.js.
            popup.setAttribute("mousethrough", "always");
            popup.setAttribute("rolluponmousewheel", "true");
            return popup;
          ]]>
        </body>
      </method>

      <method name="startScroll">
        <parameter name="scrolldir"/>
        <parameter name="screenX"/>
        <parameter name="screenY"/>
        <body><![CDATA[
            if (!this._autoScrollPopup) {
              if (this.hasAttribute("autoscrollpopup")) {
                // our creator provided a popup to share
                this._autoScrollPopup = document.getElementById(this.getAttribute("autoscrollpopup"));
              }
              else {
                // we weren't provided a popup; we have to use the global scope
                this._autoScrollPopup = this._createAutoScrollPopup();
                document.documentElement.appendChild(this._autoScrollPopup);
                this._autoScrollNeedsCleanup = true;
              }
            }

            // we need these attributes so themers don't need to create per-platform packages
            if (screen.colorDepth > 8) { // need high color for transparency
              // Exclude second-rate platforms
              this._autoScrollPopup.setAttribute("transparent", !/BeOS|OS\/2/.test(navigator.appVersion));
              // Enable translucency on Windows and Mac
              this._autoScrollPopup.setAttribute("translucent", /Win|Mac/.test(navigator.platform));
            }

            this._autoScrollPopup.setAttribute("noautofocus", "true");
            this._autoScrollPopup.setAttribute("scrolldir", scrolldir);
            this._autoScrollPopup.addEventListener("popuphidden", this, true);
            this._autoScrollPopup.showPopup(document.documentElement,
                                            screenX,
                                            screenY,
                                            "popup", null, null);
            this._ignoreMouseEvents = true;
            this._scrolling = true;
            this._startX = screenX;
            this._startY = screenY;

            window.addEventListener("mousemove", this, true);
            window.addEventListener("mousedown", this, true);
            window.addEventListener("mouseup", this, true);
            window.addEventListener("DOMMouseScroll", this, true);
            window.addEventListener("contextmenu", this, true);
            window.addEventListener("keydown", this, true);
            window.addEventListener("keypress", this, true);
            window.addEventListener("keyup", this, true);
         ]]>
        </body>
      </method>

      <method name="handleEvent">
        <parameter name="aEvent"/>
        <body>
        <![CDATA[
          if (this._scrolling) {
            switch (aEvent.type) {
              case "mousemove": {
                var x = aEvent.screenX - this._startX;
                var y = aEvent.screenY - this._startY;

                if ((x > this._AUTOSCROLL_SNAP || x < -this._AUTOSCROLL_SNAP) ||
                    (y > this._AUTOSCROLL_SNAP || y < -this._AUTOSCROLL_SNAP))
                  this._ignoreMouseEvents = false;
                break;
              }
              case "mouseup":
              case "mousedown":
              case "contextmenu": {
                if (!this._ignoreMouseEvents) {
                  // Use a timeout to prevent the mousedown from opening the popup again.
                  // Ideally, we could use preventDefault here, but contenteditable
                  // and middlemouse paste don't interact well. See bug 1188536.
                  setTimeout(() => this._autoScrollPopup.hidePopup(), 0);
                }
                this._ignoreMouseEvents = false;
                break;
              }
              case "DOMMouseScroll": {
                this._autoScrollPopup.hidePopup();
                aEvent.preventDefault();
                break;
              }
              case "popuphidden": {
                this._autoScrollPopup.removeEventListener("popuphidden", this, true);
                this.stopScroll();
                break;
              }
              case "keydown": {
                if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
                  // the escape key will be processed by
                  // nsXULPopupManager::KeyDown and the panel will be closed.
                  // So, don't consume the key event here.
                  break;
                }
                // don't break here. we need to eat keydown events.
              }
              case "keypress":
              case "keyup": {
                // All keyevents should be eaten here during autoscrolling.
                aEvent.stopPropagation();
                aEvent.preventDefault();
                break;
              }
            }
          }
        ]]>
        </body>
      </method>

      <method name="closeBrowser">
        <body>
        <![CDATA[
          // The request comes from a XPCOM component, we'd want to redirect
          // the request to tabbrowser.
          let tabbrowser = this.getTabBrowser();
          if (tabbrowser) {
            let tab = tabbrowser.getTabForBrowser(this);
            if (tab) {
              tabbrowser.removeTab(tab);
              return;
            }
          }

          throw new Error("Closing a browser which was not attached to a tabbrowser is unsupported.");
        ]]>
        </body>
      </method>

      <method name="swapBrowsers">
        <parameter name="aOtherBrowser"/>
        <body>
        <![CDATA[
          // The request comes from a XPCOM component, we'd want to redirect
          // the request to tabbrowser so tabbrowser will be setup correctly,
          // and it will eventually call swapDocShells.
          let tabbrowser = this.getTabBrowser();
          if (tabbrowser) {
            let tab = tabbrowser.getTabForBrowser(this);
            if (tab) {
              tabbrowser.swapBrowsers(tab, aOtherBrowser);
              return;
            }
          }

          // If we're not attached to a tabbrowser, just swap.
          this.swapDocShells(aOtherBrowser);
        ]]>
        </body>
      </method>

      <method name="swapDocShells">
        <parameter name="aOtherBrowser"/>
        <body>
        <![CDATA[
          // Give others a chance to swap state.
          // IMPORTANT: Since a swapDocShells call does not swap the messageManager
          //            instances attached to a browser to aOtherBrowser, others
          //            will need to add the message listeners to the new
          //            messageManager.
          //            This is not a bug in swapDocShells or the FrameLoader,
          //            merely a design decision: If message managers were swapped,
          //            so that no new listeners were needed, the new
          //            aOtherBrowser.messageManager would have listeners pointing
          //            to the JS global of the current browser, which would rather
          //            easily create leaks while swapping.
          // IMPORTANT2: When the current browser element is removed from DOM,
          //             which is quite common after a swpDocShells call, its
          //             frame loader is destroyed, and that destroys the relevant
          //             message manager, which will remove the listeners.
          let event = new CustomEvent("SwapDocShells", {"detail": aOtherBrowser});
          this.dispatchEvent(event);
          event = new CustomEvent("SwapDocShells", {"detail": this});
          aOtherBrowser.dispatchEvent(event);

          // We need to swap fields that are tied to our docshell or related to
          // the loaded page
          // Fields which are built as a result of notifactions (pageshow/hide,
          // DOMLinkAdded/Removed, onStateChange) should not be swapped here,
          // because these notifications are dispatched again once the docshells
          // are swapped.
          var fieldsToSwap = [
            "_docShell",
            "_webBrowserFind",
            "_contentWindow",
            "_webNavigation"
          ];

          var ourFieldValues = {};
          var otherFieldValues = {};
          for (let field of fieldsToSwap) {
            ourFieldValues[field] = this[field];
            otherFieldValues[field] = aOtherBrowser[field];
          }

          if (window.PopupNotifications)
            PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);

          try {
            this.swapFrameLoaders(aOtherBrowser);
          } catch (ex) {
            // This may not be implemented for browser elements that are not
            // attached to a BrowserDOMWindow.
          }

          for (let field of fieldsToSwap) {
            this[field] = otherFieldValues[field];
            aOtherBrowser[field] = ourFieldValues[field];
          }

          // Null the current nsITypeAheadFind instances so that they're
          // lazily re-created on access. We need to do this because they
          // might have attached the wrong docShell.
          this._fastFind = aOtherBrowser._fastFind = null;

          event = new CustomEvent("EndSwapDocShells", {"detail": aOtherBrowser});
          this.dispatchEvent(event);
          event = new CustomEvent("EndSwapDocShells", {"detail": this});
          aOtherBrowser.dispatchEvent(event);
        ]]>
        </body>
      </method>

      <method name="getInPermitUnload">
        <parameter name="aCallback"/>
        <body>
        <![CDATA[
          if (!this.docShell || !this.docShell.contentViewer) {
            aCallback(false);
            return;
          }
          aCallback(this.docShell.contentViewer.inPermitUnload);
        ]]>
        </body>
      </method>

      <method name="permitUnload">
        <body>
        <![CDATA[
          if (!this.docShell || !this.docShell.contentViewer) {
            return {permitUnload: true, timedOut: false};
          }
          return {permitUnload: this.docShell.contentViewer.permitUnload(), timedOut: false};
        ]]>
        </body>
      </method>

      <method name="print">
        <parameter name="aOuterWindowID"/>
        <parameter name="aPrintSettings"/>
        <parameter name="aPrintProgressListener"/>
        <body>
          <![CDATA[
            var owner = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner);
            if (!owner.frameLoader) {
              throw Components.Exception("No frame loader.",
                                         Components.results.NS_ERROR_FAILURE);
            }

            owner.frameLoader.print(aOuterWindowID, aPrintSettings,
                                    aPrintProgressListener);
          ]]>
        </body>
      </method>

      <method name="dropLinks">
        <parameter name="aLinksCount"/>
        <parameter name="aLinks"/>
        <body><![CDATA[
          if (!this.droppedLinkHandler) {
            return false;
          }
          let links = [];
          for (let i = 0; i < aLinksCount; i += 3) {
            links.push({
              url: aLinks[i],
              name: aLinks[i + 1],
              type: aLinks[i + 2],
            });
          }
          this.droppedLinkHandler(null, links);
          return true;
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="keypress" keycode="VK_F7" group="system">
        <![CDATA[
          if (event.defaultPrevented || !event.isTrusted)
            return;

          const kPrefShortcutEnabled = "accessibility.browsewithcaret_shortcut.enabled";
          const kPrefWarnOnEnable    = "accessibility.warn_on_browsewithcaret";
          const kPrefCaretBrowsingOn = "accessibility.browsewithcaret";

          var isEnabled = this.mPrefs.getBoolPref(kPrefShortcutEnabled);
          if (!isEnabled)
            return;

          // Toggle browse with caret mode
          var browseWithCaretOn = this.mPrefs.getBoolPref(kPrefCaretBrowsingOn, false);
          var warn = this.mPrefs.getBoolPref(kPrefWarnOnEnable, true);
          if (warn && !browseWithCaretOn) {
            var checkValue = {value:false};
            var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
                                          .getService(Components.interfaces.nsIPromptService);

            var buttonPressed = promptService.confirmEx(window,
              this.mStrBundle.GetStringFromName('browsewithcaret.checkWindowTitle'),
              this.mStrBundle.GetStringFromName('browsewithcaret.checkLabel'),
              // Make "No" the default:
              promptService.STD_YES_NO_BUTTONS | promptService.BUTTON_POS_1_DEFAULT,
              null, null, null, this.mStrBundle.GetStringFromName('browsewithcaret.checkMsg'),
              checkValue);
            if (buttonPressed != 0) {
              if (checkValue.value) {
                try {
                  this.mPrefs.setBoolPref(kPrefShortcutEnabled, false);
                } catch (ex) {
                }
              }
              return;
            }
            if (checkValue.value) {
              try {
                this.mPrefs.setBoolPref(kPrefWarnOnEnable, false);
              }
              catch (ex) {
              }
            }
          }

          // Toggle the pref
          try {
            this.mPrefs.setBoolPref(kPrefCaretBrowsingOn, !browseWithCaretOn);
          } catch (ex) {
          }
        ]]>
      </handler>
      <handler event="dragover" group="system">
      <![CDATA[
        if (!this.droppedLinkHandler || event.defaultPrevented)
          return;

        // For drags that appear to be internal text (for example, tab drags),
        // set the dropEffect to 'none'. This prevents the drop even if some
        // other listener cancelled the event.
        var types = event.dataTransfer.types;
        if (types.includes("text/x-moz-text-internal") && !types.includes("text/plain")) {
          event.dataTransfer.dropEffect = "none";
          event.stopPropagation();
          event.preventDefault();
        }

        let linkHandler = Components.classes["@mozilla.org/content/dropped-link-handler;1"].
                            getService(Components.interfaces.nsIDroppedLinkHandler);
        if (linkHandler.canDropLink(event, false))
          event.preventDefault();
      ]]>
      </handler>
      <handler event="drop" group="system">
      <![CDATA[
        if (!this.droppedLinkHandler || event.defaultPrevented)
          return;

        let name = { };
        let linkHandler = Components.classes["@mozilla.org/content/dropped-link-handler;1"].
                            getService(Components.interfaces.nsIDroppedLinkHandler);
        try {
          // Pass true to prevent the dropping of javascript:/data: URIs
          var links = linkHandler.dropLinks(event, true);
        } catch (ex) {
          return;
        }

        if (links.length) {
          this.droppedLinkHandler(event, links);
        }
      ]]>
      </handler>
    </handlers>

  </binding>

</bindings>
